package com.jprocms.module.cms.service.staticpage;

import com.jprocms.module.cms.controller.admin.channel.vo.ChannelListReqVO;
import com.jprocms.module.cms.controller.admin.content.vo.ContentSideReqVO;
import com.jprocms.module.cms.dal.dataobject.channel.ChannelDO;
import com.jprocms.module.cms.dal.dataobject.content.ContentDO;
import com.jprocms.module.cms.dal.dataobject.site.SiteDO;
import com.jprocms.module.cms.dal.dataobject.task.TaskDO;
import com.jprocms.module.cms.dal.dataobject.tplinclude.TplIncludeDO;
import com.jprocms.module.cms.service.channel.ChannelService;
import com.jprocms.module.cms.service.content.ContentService;
import com.jprocms.module.cms.service.task.TaskService;
import com.jprocms.module.cms.service.tplinclude.TplIncludeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import static com.jpro.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.jprocms.module.cms.config.ThreadPoolConfiguration.CMS_STATICPAGE_THREAD_POOL_TASK_EXECUTOR;
import static com.jprocms.module.cms.enums.ErrorCodeConstants.TASK_STATICPAGE_ERROR;

/**
 * HTML 静态化生成
 *
 * @author jprocms
 */
@Slf4j
@Component
public class StaticHtmlProducer extends AbstractProducer {
    private final ChannelService channelService;
    private final StaticHtmlService htmlService;

    private final TplIncludeService tplIncludeService;

    public StaticHtmlProducer(ContentService contentService, ChannelService channelService, TplIncludeService tplIncludeService, StaticHtmlService htmlService,
                              TaskService taskService, @Qualifier(CMS_STATICPAGE_THREAD_POOL_TASK_EXECUTOR) ThreadPoolTaskExecutor executor) {
        super(contentService, taskService, executor);
        this.channelService = channelService;
        this.htmlService = htmlService;
        this.tplIncludeService = tplIncludeService;
    }

    /**
     * 更新所有静态页
     *
     * @param taskSiteId 任务站点ID
     * @param taskUserId 任务用户ID
     * @param taskName   任务名称
     * @param site       需要生成静态页的站点
     */
    public void updateAllHtml(Long taskSiteId, Long taskUserId, String taskName, SiteDO site) {
        execute(taskSiteId, taskUserId, taskName, TaskDO.TYPE_HTML, false,
                taskId -> {
                    handleContent(taskId, site.getId(), htmlService::updateContentHtml);
                    handleChannel(taskId, site.getId(), htmlService::updateChannelHtml);
                    htmlService.updateIndexHtml(site);
                });
    }

    /**
     * 文章发布或者下线更新文章相关静态页。包括上一篇 下一篇 所属栏目 上级栏目 首页
     *
     * @param taskSiteId    任务站点ID
     * @param taskUserId    任务用户ID
     * @param taskName      任务名称
     * @param contentDOS    内容列表
     * @param origChannelId 文章原栏目
     */
    public void updateContentRelatedHtml(Long taskSiteId, Long taskUserId, String taskName,
                                         Collection<ContentDO> contentDOS, @Nullable Long origChannelId) {
        Set<ContentDO> contents = new HashSet<>(contentDOS.size());
        for (ContentDO contentDO : contentDOS) {
            Long contentId = contentDO.getId();
            Long channelId = contentDO.getChannelId();
            // 当前文章
            Optional.ofNullable(contentService.getContent(contentId)).filter(it -> it.getSite().getOpenSiteStaticPage()).ifPresent(contents::add);
            // 上一篇文章
            Optional.ofNullable(contentService.findSide(new ContentSideReqVO(contentId, channelId, contentDO.getSiteId(), contentDO.getReleaseTime(), contentDO.getTopLevel()), false))
                    .filter(it -> it.getSite().getOpenSiteStaticPage()).ifPresent(contents::add);
            // 下一篇文章
            Optional.ofNullable(contentService.findSide(new ContentSideReqVO(contentId, channelId, contentDO.getSiteId(), contentDO.getReleaseTime(), contentDO.getTopLevel()), true))
                    .filter(it -> it.getSite().getOpenSiteStaticPage()).ifPresent(contents::add);
            // 原栏目 上一篇、下一篇 文章
            if (origChannelId != null && !origChannelId.equals(channelId)) {
                Optional.ofNullable(contentService.findSide(new ContentSideReqVO(contentId, origChannelId, contentDO.getSiteId(), contentDO.getReleaseTime(), contentDO.getTopLevel()), false))
                        .filter(it -> it.getSite().getOpenSiteStaticPage()).ifPresent(contents::add);
                Optional.ofNullable(contentService.findSide(new ContentSideReqVO(contentId, origChannelId, contentDO.getSiteId(), contentDO.getReleaseTime(), contentDO.getTopLevel()), true))
                        .filter(it -> it.getSite().getOpenSiteStaticPage()).ifPresent(contents::add);
            }
        }
        if (contents.isEmpty()) {
            return;
        }
        Set<ChannelDO> channels = new HashSet<>(contents.size());
        for (ChannelDO channel : contents.stream().map(ContentDO::getChannel).collect(Collectors.toSet())) {
            do {
                // 文章所属栏目及上级栏目
                channels.add(channel);
                channel = channel.getParent();
            } while (channel != null);
        }
        // 原栏目
        Optional.ofNullable(origChannelId).map(channelService::getChannel).ifPresent(channels::add);
        Set<SiteDO> sites = channels.stream().map(ChannelDO::getSite).collect(Collectors.toSet());

        execute(taskSiteId, taskUserId, taskName, TaskDO.TYPE_HTML, true,
                taskId -> {
                    try {
                        // 更新文章HTML
                        for (ContentDO content : contents) {
                            htmlService.updateContentHtml(content);
                        }
                        // 更新栏目HTML
                        for (ChannelDO channel : channels) {
                            htmlService.updateChannelHtml(channel);
                        }
                        // 更新首页HTML
                        for (SiteDO site : sites) {
                            htmlService.updateIndexHtml(site);
                        }
                    } catch (Exception e) {
                        log.error("文章发布或者下线更新文章相关静态页失败,{}", e.getMessage());
                        throw exception(TASK_STATICPAGE_ERROR);
                    }
                }
        );
    }

    /**
     * 更新栏目相关静态页。包括当前栏目 上级栏目 首页
     *
     * @param taskSiteId 任务站点ID
     * @param taskUserId 任务用户ID
     * @param taskName   任务名称
     * @param channelId  栏目ID
     */
    public void updateChannelRelatedHtml(Long taskSiteId, Long taskUserId, String taskName, Long channelId) {
        // 所属栏目、所属栏目的上级栏目、首页
        ChannelDO channel = channelService.getChannel(channelId);
        if (channel == null) {
            return;
        }
        execute(taskSiteId, taskUserId, taskName, TaskDO.TYPE_HTML, true,
                taskId -> {
                    try {
                        htmlService.updateChannelHtml(channel);
                        ChannelDO parent = channel.getParent();
                        while (parent != null) {
                            htmlService.updateChannelHtml(parent);
                            parent = parent.getParent();
                        }
                        // 首页
                        htmlService.updateIndexHtml(channel.getSite());
                    } catch (Exception e) {
                        log.error("文章发布或者下线更新文章相关静态页失败,{}", e.getMessage());
                        throw exception(TASK_STATICPAGE_ERROR);
                    }
                }
        );
    }

    /**
     * 更新文章静态页
     *
     * @param taskSiteId 任务站点ID
     * @param taskUserId 任务用户ID
     * @param taskName   任务名称
     * @param site       需要生成静态页的站点
     */
    public void updateContentHtml(Long taskSiteId, Long taskUserId, String taskName, SiteDO site) {
        execute(taskSiteId, taskUserId, taskName, TaskDO.TYPE_HTML, false,
                taskId -> {
                    handleContent(taskId, site.getId(), contentDO -> {
                        try {
                            htmlService.updateContentHtml(contentDO);
                        } catch (Exception e) {
                            log.error("更新文章静态页失败,{}", e.getMessage());
                            throw exception(TASK_STATICPAGE_ERROR);
                        }
                    });
                });
    }

    /**
     * 生成栏目静态页
     *
     * @param taskSiteId 任务站点ID
     * @param taskUserId 任务用户ID
     * @param taskName   任务名称
     * @param site       需要生成静态页的站点
     */
    public void updateChannelHtml(Long taskSiteId, Long taskUserId, String taskName, SiteDO site) {
        execute(taskSiteId, taskUserId, taskName, TaskDO.TYPE_HTML, false, taskId -> handleChannel(taskId, site.getId(), htmlService::updateChannelHtml));
    }

    private void handleChannel(Long taskId, Long siteId, Consumer<ChannelDO> consumer) {
        ChannelListReqVO channelListReqVO = ChannelListReqVO.of(siteId, null, null, null);
        List<ChannelDO> channels = channelService.getChannelListForFront(channelListReqVO);
        for (ChannelDO channel : channels) {
            consumer.accept(channel);
            if (super.updateTask(taskId, 1)) {
                break;
            }
        }
    }

    /**
     * 生成包含模板静态页
     *
     * @param taskSiteId 任务站点ID
     * @param taskUserId 任务用户ID
     * @param taskName   任务名称
     * @param site       需要生成静态页的站点
     */
    public void updateIncludeHtml(Long taskSiteId, Long taskUserId, String taskName, SiteDO site) {
        execute(taskSiteId, taskUserId, taskName, TaskDO.TYPE_HTML, false, taskId -> handleIncludePage(taskId, site.getId(), htmlService::updateIncludePage));
    }

    private void handleIncludePage(Long taskId, Long siteId, Consumer<TplIncludeDO> consumer) {
        final List<TplIncludeDO> tplIncludeList = tplIncludeService.getTplIncludeList(siteId);
        for (TplIncludeDO tplIncludeDO : tplIncludeList) {
            consumer.accept(tplIncludeDO);
            if (super.updateTask(taskId, 1)) {
                break;
            }
        }
    }

}
